/*
 * Copyright [2019] [Doric.Pub]
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
//
//  DoricContext.m
//  Doric
//
//  Created by pengfei.zhou on 2019/7/25.
//

#import "DoricContext.h"
#import "DoricContextManager.h"
#import "DoricRootNode.h"
#import "DoricConstant.h"
#import "DoricExtensions.h"
#import "DoricNativeDriver.h"
#import "DoricUtil.h"
#import "DoricSingleton.h"
#import "DoricShaderPlugin.h"
#import <JavaScriptCore/JavaScriptCore.h>

@implementation DoricContext

- (instancetype)initWithScript:(NSString *)script source:(NSString *)source extra:(NSString *)extra {
    if (self = [super init]) {
        _driver = DoricSingleton.instance.nativeDriver;
        _pluginInstanceMap = [NSMutableDictionary new];
        _script = script;
        _source = source;
        _initialParams = [@{@"width": @(0), @"height": @(0)} mutableCopy];
        [[DoricContextManager instance] createContext:self script:script source:source];
        _headNodes = [NSMutableDictionary new];
        DoricRootNode *rootNode = [[DoricRootNode alloc] initWithContext:self];
        _rootNode = rootNode;
        _cachedResources = [[NSMapTable alloc] initWithKeyOptions:NSPointerFunctionsCopyIn
                                                     valueOptions:NSPointerFunctionsWeakMemory
                                                         capacity:0];
        [self init:extra];
        [self callEntity:DORIC_ENTITY_CREATE withArgumentsArray:@[]];
    }
    return self;
}

- (DoricViewNode *)targetViewNode:(NSString *)viewId {
    if ([self.rootNode.viewId isEqualToString:viewId]) {
        return self.rootNode;
    } else {
        for (NSMutableDictionary <NSString *, DoricViewNode *> *map in self.headNodes.allValues) {
            if ([[map allKeys] containsObject:viewId]) {
                return map[viewId];
            }
        }
    }
    return nil;
}

- (void)dealloc {
    _destroyed = YES;
    [[DoricContextManager instance] destroyContext:self];
    [self callEntity:DORIC_ENTITY_DESTROY withArgumentsArray:@[]];
    [self.driver destroyContext:self.contextId];
    [self.cachedResources removeAllObjects];
}

- (DoricAsyncResult *)callEntity:(NSString *)method, ... {
    va_list args;
    va_start(args, method);
    DoricAsyncResult *ret = [self.driver invokeContextEntity:self.contextId method:method arguments:args];
    va_end(args);
    return ret;
}

- (DoricAsyncResult *)callEntity:(NSString *)method withArguments:(va_list)args {
    return [self.driver invokeContextEntity:self.contextId method:method arguments:args];
}

- (DoricAsyncResult *)callEntity:(NSString *)method withArgumentsArray:(NSArray *)args {
    return [self.driver invokeContextEntity:self.contextId method:method argumentsArray:args];
}

- (void)init:(NSString *)initData {
    if (DoricSingleton.instance.enableRecordSnapshot) {
        [self callEntity:@"__enableSnapshot__" withArgumentsArray:@[]];
    }
    self.extra = initData;
    if (initData) {
        [self callEntity:DORIC_ENTITY_INIT withArgumentsArray:@[initData]];
    }
}

- (void)build:(CGSize)size {
    [self.initialParams also:^(NSMutableDictionary *it) {
        it[@"width"] = @(size.width);
        it[@"height"] = @(size.height);
    }];
    [self callEntity:DORIC_ENTITY_BUILD withArgumentsArray:@[self.initialParams]];
}

- (void)reload:(NSString *)script {
    [[self.driver destroyContext:self.contextId] waitUntilResult];
    self.rootNode.viewId = nil;
    [self.rootNode clearSubModel];
    [self.rootNode.view.subviews forEach:^(__kindof UIView *obj) {
        [obj removeFromSuperview];
    }];
    self.script = script;
    _destroyed = YES;
    [self dispatchToMainQueue:^{
        _destroyed = NO;
        [self.driver createContext:self.contextId script:script source:self.source];
        [self init:self.extra];
        [self callEntity:DORIC_ENTITY_CREATE withArgumentsArray:@[]];
        [self callEntity:DORIC_ENTITY_BUILD withArgumentsArray:@[self.initialParams]];
        [self onShow];
    }];
}

- (void)onShow {
    [self callEntity:DORIC_ENTITY_SHOW withArgumentsArray:@[]];
}

- (void)onHidden {
    [self callEntity:DORIC_ENTITY_HIDDEN withArgumentsArray:@[]];
}

- (void)onEnvChanged {
    [self callEntity:DORIC_ENTITY_ENV_CHANGE withArgumentsArray:@[]];
}

- (UIViewController *)vc {
    if (!_vc) {
        return [UIApplication sharedApplication].keyWindow.rootViewController;
    }
    return _vc;
}

- (void)dispatchToMainQueue:(_Nonnull dispatch_block_t)block {
    dispatch_async(dispatch_get_main_queue(), ^{
        @try {
            block();
        } @catch (NSException *exception) {
            DoricLog(@"dispatchToMainQueue Error:%@", exception.reason);
            [self.driver.registry onException:exception inContext:self];
        }
    });
}

- (DoricAsyncResult *)pureCallEntity:(NSString *)method withArgumentsArray:(NSArray *)args {
    NSMutableArray *array = [[NSMutableArray alloc] init];
    [array addObject:self.contextId];
    [array addObject:method];
    [array addObjectsFromArray:args];
    DoricAsyncResult *ret = [self.driver invokeDoricMethod:DORIC_CONTEXT_INVOKE_PURE argumentsArray:array];
    __weak typeof(self) __self = self;
    ret.exceptionCallback = ^(NSException *e) {
        __strong typeof(__self) self = __self;
        [self.driver.registry
                onException:e
                  inContext:self];
    };
    return ret;
}

- (void)renderSynchronously {
    DoricAsyncResult *ret = [self pureCallEntity:DORIC_ENTITY_FETCH_DIRTY_DATA withArgumentsArray:@[]];
    NSArray *array = [ret waitUntilResult:^(JSValue *models) {
        return [models toArray];
    }];
    DoricShaderPlugin *shaderPlugin = self.pluginInstanceMap[@"Shader"];
    if (!shaderPlugin) {
        shaderPlugin = [[DoricShaderPlugin alloc] initWithContext:self];
        self.pluginInstanceMap[@"Shader"] = shaderPlugin;
    }
    for (NSDictionary *model in array) {
        [shaderPlugin render:model withPromise:nil];
    }
}

@end
